module cp0(
    input         clock,
    input         reset,
    input  [ 5:0] ext_int_in,
    input         wb_valid,
    input         op_mtc0,
    input         op_tlbp,
    input         op_tlbr,
    input         inst_retake,
    input         wb_ex,
    input         wb_bd,
    input  [ 4:0] wb_excode,
    input  [31:0] wb_badvaddr,
    input  [31:0] wb_pc,
    input         eret_flush,
    input  [ 7:0] c0_addr,
    input  [31:0] c0_wdata,
    output [31:0] c0_rdata,
    output [31:0] epc,
    output        exl,
    output        has_int,
    output [ 7:0] asid,
    output [18:0] vpn2,
    //tlb write port
    output [ 3:0] w_index, 
    output [18:0]               w_vpn2, 
    output [ 7:0]               w_asid, 
    output                      w_g, 
    output [19:0]               w_pfn0,
    output [ 2:0]               w_c0, 
    output                      w_d0, 
    output                      w_v0, 
    output [19:0]               w_pfn1, 
    output [ 2:0]               w_c1, 
    output                      w_d1, 
    output                      w_v1,
    //tlb read port
    output [3:0] r_index, 
    input [18:0]                r_vpn2, 
    input [ 7:0]                r_asid, 
    input                       r_g, 
    input [19:0]                r_pfn0, 
    input [ 2:0]                r_c0, 
    input                       r_d0, 
    input                       r_v0, 
    input [19:0]                r_pfn1, 
    input [ 2:0]                r_c1, 
    input                       r_d1, 
    input                       r_v1
);

wire count_eq_compare;
wire c0_we;
wire tlbr;
wire tlbp;
assign c0_we = wb_valid && op_mtc0 && !wb_ex && !inst_retake;
assign tlbp = wb_valid && op_tlbp && !wb_ex && !inst_retake;
assign tlbr = wb_valid && op_tlbr && !wb_ex && !inst_retake;

/* Status */
//Status_BEV
wire c0_status_bev; 
assign c0_status_bev = 1'b1;
//Status_IM
reg [ 7:0] c0_status_im; 
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_STATUS) 
        c0_status_im <= c0_wdata[15:8]; 
end
//Status_EXL
reg c0_status_exl; 
always @(posedge clock) begin 
    if (reset) 
        c0_status_exl <= 1'b0; 
    else if (wb_ex) 
        c0_status_exl <= 1'b1; 
    else if (eret_flush) 
        c0_status_exl <= 1'b0; 
    else if (c0_we && c0_addr==`CR_STATUS) 
        c0_status_exl <= c0_wdata[1]; 
end
//Status_IE
reg c0_status_ie;
always @(posedge clock) begin 
    if (reset) 
        c0_status_ie <= 1'b0;
    else if (c0_we && c0_addr==`CR_STATUS) 
        c0_status_ie <= c0_wdata[0]; 
end

/* Cause */
//Cause_BD
reg c0_cause_bd; 
always @(posedge clock) begin 
    if (reset) 
        c0_cause_bd <= 1'b0; 
    else if (wb_ex && !c0_status_exl) 
        c0_cause_bd <= wb_bd; 
end
//Cause_TI
reg c0_cause_ti; 
always @(posedge clock) begin 
    if (reset) 
        c0_cause_ti <= 1'b0; 
    else if (c0_we && c0_addr==`CR_COMPARE) 
        c0_cause_ti <= 1'b0; 
    else if (count_eq_compare) 
        c0_cause_ti <= 1'b1; 
end
//Cause_IP
reg [ 7:0] c0_cause_ip; 
always @(posedge clock) begin 
    if (reset) 
        c0_cause_ip[7:2] <= 6'd0; 
    else begin 
        c0_cause_ip[7] <= ext_int_in[5] | c0_cause_ti; 
        c0_cause_ip[6:2] <= ext_int_in[4:0]; 
    end 
end
always @(posedge clock) begin 
    if (reset) 
        c0_cause_ip[1:0] <= 2'd0; 
    else if (c0_we && c0_addr==`CR_CAUSE) 
        c0_cause_ip[1:0] <= c0_wdata[9:8]; 
end
//Cause_EXCODE
reg [ 4:0] c0_cause_excode; 
always @(posedge clock) begin 
    if (reset) 
        c0_cause_excode <= 5'd0; 
    else if (wb_ex) 
        c0_cause_excode <= wb_excode; 
end

/* Epc */
reg [31:0] c0_epc; 
always @(posedge clock) begin 
    if (wb_ex && !c0_status_exl) 
        c0_epc <= wb_bd ? wb_pc - 3'h4 : wb_pc; 
    else if (c0_we && c0_addr==`CR_EPC) 
        c0_epc <= c0_wdata; 
end

/* BADVADDR */
wire badvaddr_we;
assign badvaddr_we = wb_ex && ((wb_excode==`EX_ADEL) || (wb_excode == `EX_ADES) || (wb_excode == `EX_TLBL) || (wb_excode == `EX_TLBS) || (wb_excode == `EX_MOD));
reg [31:0] c0_badvaddr; 
always @(posedge clock) begin 
    if (badvaddr_we) 
        c0_badvaddr <= wb_badvaddr; 
end

/* COUNT */
reg tick; 
reg [31:0] c0_count; 
always @(posedge clock) begin 
    if (reset) 
        tick <= 1'b0;
    else tick <= ~tick; 
    
    if (c0_we && c0_addr==`CR_COUNT) 
        c0_count <= c0_wdata; 
    else if (tick) 
        c0_count <= c0_count + 1'b1; 
end

/* COMPARE */
reg [31:0] c0_compare;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_COMPARE) 
        c0_compare <= c0_wdata[31:0]; 
end

/* EntryHi */
//EntryHi_VPN2
reg [18:0] c0_entryhi_vpn2;
always @(posedge clock) begin 
    if (wb_ex && ((wb_excode == `EX_TLBL) || (wb_excode == `EX_TLBS) || (wb_excode == `EX_MOD)))
        c0_entryhi_vpn2 <= c0_wdata[31:13];
    else if (c0_we && c0_addr==`CR_ENTRYHI) 
        c0_entryhi_vpn2 <= c0_wdata[31:13];
    else if (tlbr)
        c0_entryhi_vpn2 <= r_vpn2;
end
//EntryHi_ASID
reg [ 7:0] c0_entryhi_asid;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYHI) 
        c0_entryhi_asid <= c0_wdata[7:0];
    else if (tlbr)
        c0_entryhi_asid <= r_asid;
end

/* EntryLo0 */
//EntryLo0_PFN0
reg [19:0] c0_entrylo0_pfn0;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO0) 
        c0_entrylo0_pfn0 <= c0_wdata[25:6];
    else if (tlbr)
        c0_entrylo0_pfn0 <= r_pfn0;
end
//EntryLo0_C0
reg [ 2:0] c0_entrylo0_c0;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO0) 
        c0_entrylo0_c0 <= c0_wdata[5:3];
    else if (tlbr)
        c0_entrylo0_c0 <= r_c0;
end
//EntryLo0_D0
reg c0_entrylo0_d0;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO0) 
        c0_entrylo0_d0 <= c0_wdata[2];
    else if (tlbr)
        c0_entrylo0_d0 <= r_d0;
end
//EntryLo0_V0
reg c0_entrylo0_v0;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO0) 
        c0_entrylo0_v0 <= c0_wdata[1];
    else if (tlbr)
        c0_entrylo0_v0 <= r_v0;
end
//EntryLo0_G0
reg c0_entrylo0_g0;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO0) 
        c0_entrylo0_g0 <= c0_wdata[0];
    else if (tlbr)
        c0_entrylo0_g0 <= r_g; 
end

/* EntryLo1 */
//EntryLo1_PFN1
reg [19:0] c0_entrylo1_pfn1;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO1) 
        c0_entrylo1_pfn1 <= c0_wdata[25:6];
    else if (tlbr)
        c0_entrylo1_pfn1 <= r_pfn1;
end
//EntryLo1_C1
reg [ 2:0] c0_entrylo1_c1;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO1) 
        c0_entrylo1_c1 <= c0_wdata[5:3];
    else if (tlbr)
        c0_entrylo1_c1 <= r_c1;
end
//EntryLo1_D1
reg c0_entrylo1_d1;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO1) 
        c0_entrylo1_d1 <= c0_wdata[2];
    else if (tlbr)
        c0_entrylo1_d1 <= r_d1;
end
//EntryLo1_V1
reg c0_entrylo1_v1;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO1) 
        c0_entrylo1_v1 <= c0_wdata[1];
    else if (tlbr)
        c0_entrylo1_v1 <= r_v1;
end
//EntryLo1_G1
reg c0_entrylo1_g1;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_ENTRYLO1) 
        c0_entrylo1_g1 <= c0_wdata[0];
    else if (tlbr)
        c0_entrylo1_g1 <= r_g; 
end

/* Index */
//Index_P
reg c0_index_p;
always @(posedge clock) begin
    if(reset)
        c0_index_p <= 1'b0;
    if (tlbp) 
        c0_index_p <= c0_wdata[31]; 
end
//Index_Index
reg [3:0] c0_index_index;
always @(posedge clock) begin 
    if (c0_we && c0_addr==`CR_INDEX) 
        c0_index_index <= c0_wdata[3:0];
    else if(tlbp)
        c0_index_index <= c0_wdata[3:0];
end

assign count_eq_compare = (c0_count == c0_compare);
assign c0_rdata = {32{c0_addr == `CR_STATUS  }} & {9'd0, c0_status_bev, 6'd0, c0_status_im, 6'd0, c0_status_exl, c0_status_ie}
                | {32{c0_addr == `CR_CAUSE   }} & {c0_cause_bd, c0_cause_ti, 14'd0, c0_cause_ip, 1'd0, c0_cause_excode, 2'd0}
                | {32{c0_addr == `CR_EPC     }} & c0_epc
                | {32{c0_addr == `CR_BADVADDR}} & c0_badvaddr
                | {32{c0_addr == `CR_COUNT   }} & c0_count
                | {32{c0_addr == `CR_COMPARE }} & c0_compare
                | {32{c0_addr == `CR_ENTRYHI }} & {c0_entryhi_vpn2, 5'd0, c0_entryhi_asid}
                | {32{c0_addr == `CR_ENTRYLO0}} & {6'd0, c0_entrylo0_pfn0, c0_entrylo0_c0, c0_entrylo0_d0, c0_entrylo0_v0, c0_entrylo0_g0}
                | {32{c0_addr == `CR_ENTRYLO1}} & {6'd0, c0_entrylo1_pfn1, c0_entrylo1_c1, c0_entrylo1_d1, c0_entrylo1_v1, c0_entrylo1_g1}
                | {32{c0_addr == `CR_INDEX   }} & {c0_index_p, 27'd0, c0_index_index};
assign epc = c0_epc;
assign exl = c0_status_exl;
assign has_int = ((c0_cause_ip[7:0] & c0_status_im[7:0]) != 8'h00) && c0_status_ie == 1'b1 && c0_status_exl == 1'b0;
assign asid = c0_entryhi_asid;
assign vpn2 = c0_entryhi_vpn2;

//tlb write port
assign w_index = c0_index_index;
assign w_vpn2 = c0_entryhi_vpn2;
assign w_asid = c0_entryhi_asid;
assign w_g = c0_entrylo0_g0 & c0_entrylo1_g1;
assign w_pfn0 = c0_entrylo0_pfn0;
assign w_c0 = c0_entrylo0_c0;
assign w_d0 = c0_entrylo0_d0;
assign w_v0 = c0_entrylo0_v0;
assign w_pfn1 = c0_entrylo1_pfn1;
assign w_c1 = c0_entrylo1_c1;
assign w_d1 = c0_entrylo1_d1;
assign w_v1 = c0_entrylo1_v1;
assign r_index = c0_index_index;

endmodule